//////////////////////////////////////////////////////////////////////////
//
// This file is a part of XORT project.
//
// PROJECT:         Xort compiler
// FILE:            XortStringImpl.cpp
// AUTHOR:          Vladimir Gumenuk
// DATE:            12-Apr-2013
// 
// NOTES: xstring implementation for XVM
//
//
//     Copyright(c) 2014, Vladimir Gumenuk
//     All rights reserved.
//
//     Redistribution and use in source and binary forms, with or without modification, 
//     are permitted provided that the following conditions are met :
//
//     1. Redistributions of source code must retain the above copyright notice, 
//        this list of conditions and the following disclaimer.
//     2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and 
//        the following disclaimer in the documentation and / or other materials provided with the distribution.
//     3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse 
//        or promote products derived from this software without specific prior written permission.
//
//     THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
//     ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 
//     OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 
//     THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
//     SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT 
//     OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
//     HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 
//     OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 
//     EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
//////////////////////////////////////////////////////////////////////////

#include "xort.h"
#include "xruntime.h"
#include "BuildinObjectInfo.h"
#include "XortStringImpl.h"
#include "xbuildin.h"


//////////////////////////////////////////////////////////////////////////
//

BuildInObjectInfo xstring::m_StringInfo;

xstring::xstring()
{
    m_bInited = FALSE;
}

xstring::xstring( const string& str ) : string( str )
{
    m_bInited = FALSE;
}

xstring::~xstring()
{
    
}

// TODO: optimize me! xstring is a build-in object and the symbol and all functions will be
// the same for all strings. Do not create it for each string!
int xstring::Init( symbolref pSymbol, XortNativeInterface *pExternalObj )
{
    if( !m_bInited )
    {
        m_bInited = TRUE;
        m_StringInfo.Init( pSymbol );
    }
    return XORT_OK;
}

void xstring::Uninit()
{
    clear();

    if( m_bInited )
    {
        m_bInited = FALSE;
        m_StringInfo.Uninit();
    }
}

symbolref xstring::get_Symbol()
{
    return m_StringInfo.get_Symbol();
}

symbolref xstring::get_Method( dword dwIdx )
{
    return m_StringInfo.get_Method( dwIdx );
}

int xstring::Invoke( dword dwFuncIndex, XRuntime *pRuntime )
{
    int iRet = XORT_OK;

    XCELLSARRAY& stack = pRuntime->get_Stack();
    const XortElement& sysFrame = stack.top();
    if( sysFrame.bAccess != sys_Frame ) return ERR_INVALID_STACK_SYSFRAME;
    dword dwArgsCount = sysFrame.bType;
    dword dwStackBase = pRuntime->get_StackBase();

    InstructionData retPrim; retPrim.m_bInstrCode = p_ret; retPrim.m_bTypeAddr = t_v; // prepare ret primitive
    XortElement retVal; retVal.bAccess = usr_VarRW; // prepare return value

    switch( dwFuncIndex )
    {
    case 1:     // 1 dword Assign ( xstring csLiteral ); s1 = s2
        {
            if( dwArgsCount != 1 ) // no arguments ??
                return ERR_INVALID_ARG_COUNT;

            XortElement& argValue  = stack.get( dwStackBase - dwArgsCount );
            if( argValue.bType != t_p ) return ERR_INVALID_TYPE;
            if( !argValue._p ) { // allows clean xstring
                Empty();
                retVal._u4 = 0;
            } else {
                if( argValue._p->GetType() != t_string )
                    return ERR_INVALID_TYPE;

                xstring *pStr = (xstring*)argValue._p;
                retVal._u4 = Assign( pStr->m_pchData );
            }

            retVal.bType = t_u4;

            iRet = pRuntime->do_ret( retPrim ); // clean the XVM stack
            if( iRet != XORT_OK )
                return iRet;

            stack.push( retVal );
            break;
        }

    case 2:     // 2 dword  Add ( xstring csLiteral );
        {
            if( dwArgsCount != 1 ) // no arguments ??
                return ERR_INVALID_ARG_COUNT;

            XortElement& argValue  = stack.get( dwStackBase - dwArgsCount );
            if( argValue.bType != t_p ) return ERR_INVALID_TYPE;
            if( !argValue._p ) { // do nothing :)

                retVal._u4 = 0;
            } else {
                if( argValue._p->GetType() != t_string )
                    return ERR_INVALID_TYPE;

                xstring *pStr = (xstring*)argValue._p;
                retVal._u4 = Add( pStr->m_pchData );
            }

            retVal.bType = t_u4;

            iRet = pRuntime->do_ret( retPrim ); // clean the XVM stack
            if( iRet != XORT_OK )
                return iRet;

            stack.push( retVal );
            break;
        }

    case 3:     // 3 dword  Append( byte ch );
        {
            if( dwArgsCount != 1 ) // no arguments ??
                return ERR_INVALID_ARG_COUNT;

            XortElement& argValue  = stack.get( dwStackBase - dwArgsCount );
            if( argValue.bType != t_u1 ) return ERR_INVALID_TYPE;

            retVal._u4 = Append( argValue._s1 );
            retVal.bType = t_u4;

            iRet = pRuntime->do_ret( retPrim ); // clean the XVM stack
            if( iRet != XORT_OK )
                return iRet;

            stack.push( retVal );
            break;
        }

    case 4:     // 4 byte   CharAt( dword dwPos );
        {
            if( dwArgsCount != 1 ) // no arguments ??
                return ERR_INVALID_ARG_COUNT;

            XortElement& argValue  = stack.get( dwStackBase - dwArgsCount );
            if( argValue.bType != t_u4 ) return ERR_INVALID_TYPE;
            retVal._u1 = CharAt( argValue._u4 );
            retVal.bType = t_u1;

            iRet = pRuntime->do_ret( retPrim ); // clean the XVM stack
            if( iRet != XORT_OK )
                return iRet;

            stack.push( retVal );
            break;
        }

    case 5:     // 5 dword  Length();
        {
            if( dwArgsCount != 0 ) // arguments ??
                return ERR_INVALID_ARG_COUNT;

            retVal._u4   = Length(); // invoke function
            retVal.bType = t_u4;

            iRet = pRuntime->do_ret( retPrim ); // clean the XVM stack
            if( iRet != XORT_OK )
                return iRet;

            stack.push( retVal ); // push the result of procedure
            break;
        }

    case 6:     // 6 void   Empty();
        {
            if( dwArgsCount != 0 ) // arguments ??
                return ERR_INVALID_ARG_COUNT;

            Empty(); // invoke function

            iRet = pRuntime->do_ret( retPrim ); // clean the XVM stack
            if( iRet != XORT_OK )
                return iRet;
            break;
        }

    case 7:     // 7 int    Compare( xstring cs );
        {
            if( dwArgsCount != 1 ) // no arguments ??
                return ERR_INVALID_ARG_COUNT;

            XortElement& argValue  = stack.get( dwStackBase - dwArgsCount );
            if( argValue.bType != t_p ) return ERR_INVALID_TYPE;
            if( !argValue._p ) { // do nothing :)

                retVal._s4 = -1; // mark that arg xstring is less... verify if this correct!
            } else {
                if( argValue._p->GetType() != t_string )
                    return ERR_INVALID_TYPE;

                xstring *pStr = (xstring*)argValue._p;
                retVal._u4 = Compare( pStr->m_pchData );
            }

            retVal.bType = t_s4;

            iRet = pRuntime->do_ret( retPrim ); // clean the XVM stack
            if( iRet != XORT_OK )
                return iRet;

            stack.push( retVal );
            break;
        }

    case 8:     // 8 double doubleValue();
        {
            if( dwArgsCount != 0 ) // arguments ??
                return ERR_INVALID_ARG_COUNT;

            retVal._r8   = doubleValue(); // invoke function
            retVal.bType = t_r8;

            iRet = pRuntime->do_ret( retPrim ); // clean the XVM stack
            if( iRet != XORT_OK )
                return iRet;

            stack.push( retVal ); // push the result of procedure
            break;
        }

    case 9:     // 9 float  floatValue();
        {
            if( dwArgsCount != 0 ) // arguments ??
                return ERR_INVALID_ARG_COUNT;

            retVal._r8   = floatValue(); // invoke function
            retVal.bType = t_r4;

            iRet = pRuntime->do_ret( retPrim ); // clean the XVM stack
            if( iRet != XORT_OK )
                return iRet;

            stack.push( retVal ); // push the result of procedure
            break;
        }

    case 10:    // 10 int   intValue();
        {
            if( dwArgsCount != 0 ) // arguments ??
                return ERR_INVALID_ARG_COUNT;

            retVal._s4   = intValue(); // invoke function
            retVal.bType = t_s4;

            iRet = pRuntime->do_ret( retPrim ); // clean the XVM stack
            if( iRet != XORT_OK )
                return iRet;

            stack.push( retVal ); // push the result of procedure
            break;
        }

    case 11:    // 11 qlong longValue();
        {
            if( dwArgsCount != 0 ) // arguments ??
                return ERR_INVALID_ARG_COUNT;

            retVal._s8   = longValue(); // invoke function
            retVal.bType = t_s8;

            iRet = pRuntime->do_ret( retPrim ); // clean the XVM stack
            if( iRet != XORT_OK )
                return iRet;

            stack.push( retVal ); // push the result of procedure
            break;
        }

    case 12:    // 12 void  Format( xstring csFmt, ... );
        {
            if( dwArgsCount == 0 ) // arguments ??
                return ERR_INVALID_ARG_COUNT;

            XortElement& argFmt  = stack.get( dwStackBase - 1 );
            if( argFmt.bType != t_p ) return ERR_INVALID_TYPE;
            if( !argFmt._p ) return ERR_INVALID_ARG;
            if( argFmt._p->GetType() != t_string ) return ERR_INVALID_TYPE;

            xstring *pStrFmt = (xstring*)argFmt._p;
            if( pStrFmt->Length() == 0 ) // nothing to format?
                return ERR_INVALID_ARG;

            // obviously, this must be corrected for 64 bit architectures...
#define MAX_CALL_STACK 100
#ifdef ARCH_64BIT
            typedef qword stkcell;
            #define STKINC 1
#elif defined(ARCH_32BIT)
            typedef dword stkcell;
            #define STKINC 2
#else
            error "Unsupported architecture!"
#endif

            dword dwStkIdx = 0; // index of the first var
            stkcell dwStk[MAX_CALL_STACK];       // hope this will be enough :)
            memset( &dwStk[0], 0, MAX_CALL_STACK * sizeof( stkcell ) );

            for( dword i = 2; i <= dwArgsCount; i++ ) // now the arguments are passed in cdecl cc!
            {
                XortElement& arg = stack.get( dwStackBase - i );
                switch( arg.bType )
                {
                case t_v: return ERR_INVALID_TYPE;
                case t_s1: dwStk[dwStkIdx] = arg._s1; dwStkIdx++; break;
                case t_u1: dwStk[dwStkIdx] = arg._u1; dwStkIdx++; break;
                case t_s2: dwStk[dwStkIdx] = arg._s2; dwStkIdx++; break; // samll correcttion for sign-extension
                case t_u2: dwStk[dwStkIdx] = arg._u2; dwStkIdx++; break;
                case t_s4: dwStk[dwStkIdx] = arg._s4; dwStkIdx++; break;
                case t_u4: dwStk[dwStkIdx] = arg._u4; dwStkIdx++; break;
                case t_s8: 
                case t_u8:
                    {
                        qlong *pQ8 = (qlong*)&dwStk[dwStkIdx];
                        *pQ8 = arg._u8;
                        dwStkIdx += STKINC;
                        break;
                    }
                case t_r4: 
                    {
                        real *pR8 = (real*)&dwStk[dwStkIdx];
                        *pR8 = (real)arg._r4;
                        dwStkIdx += STKINC;
                        break;
                    }
                case t_r8: 
                    {
                        real *pR8 = (real*)&dwStk[dwStkIdx];
                        *pR8 = arg._r8;
                        dwStkIdx += STKINC;
                        break;
                    }
                case t_p:  
                    if( arg._p ) {
                        dword dwObjType = arg._p->GetType();
                        if( dwObjType == t_string ) {
                            dwStk[dwStkIdx] = (stkcell)((xstring*)arg._p)->GetBuffer(); dwStkIdx++;
                        } else {
                            dwStk[dwStkIdx] = (stkcell)(arg._p); dwStkIdx++; // array and list and other user object must be represented as pointer :)
                        }
                    } else {
                        dwStk[dwStkIdx] = 0; dwStkIdx++;
                    }
                    break;
                default:
                    return ERR_INVALID_TYPE;
                }
            }

            va_list argPtr = (va_list)&dwStk[0]; // make pointer to arguments
            int iSize = _vscprintf( pStrFmt->GetBuffer(), argPtr );
            if( iSize )
            {
                char *pBuf = new char[iSize+1];
                if( !pBuf )
                    return ERR_MEMORY_ALLOCATION;

                clear(); // delete previous content of the current xstring

                m_dwLength = vsprintf( pBuf, pStrFmt->GetBuffer(), argPtr );
                m_pchData = pBuf;
                
            } else {
                clear();
            }

            iRet = pRuntime->do_ret( retPrim ); // clean the XVM stack
            if( iRet != XORT_OK )
                return iRet;

            retVal._u4 = m_dwLength;
            retVal.bType = t_u4;
            stack.push( retVal );
            break;
        }

    case 13:    // 13 dword Replace( xstring strOld, xstring strNew, dword dwNumReplacements );
        {
            if( dwArgsCount != 3 ) // no arguments ??
                return ERR_INVALID_ARG_COUNT;

            XortElement& argValue1  = stack.get( dwStackBase - dwArgsCount );
            if( argValue1.bType != t_p ) return ERR_INVALID_TYPE;
            if( !argValue1._p )
                return ERR_INVALID_ARG;

            dwArgsCount -= 1;
            XortElement& argValue2  = stack.get( dwStackBase - dwArgsCount );
            if( argValue2.bType != t_p ) return ERR_INVALID_TYPE;
            if( !argValue2._p )
                return ERR_INVALID_ARG;

            dwArgsCount -= 1;
            XortElement& argValue3  = stack.get( dwStackBase - dwArgsCount );
            if( argValue3.bType != t_u4 ) return ERR_INVALID_TYPE;

            if( argValue1._p->GetType() != t_string )
                return ERR_INVALID_TYPE;

            if( argValue2._p->GetType() != t_string )
                return ERR_INVALID_TYPE;

            xstring *pStr1 = (xstring*)argValue1._p;
            xstring *pStr2 = (xstring*)argValue2._p;

            retVal._u4   = Replace( pStr1->m_pchData, pStr2->m_pchData, argValue3._u4 );
            retVal.bType = t_u4;

            iRet = pRuntime->do_ret( retPrim ); // clean the XVM stack
            if( iRet != XORT_OK )
                return iRet;

            stack.push( retVal );
            break;
        }

    case 14:    // 14 dword hasChr( byte ch )
        {
            if( dwArgsCount != 1 ) // no arguments ??
                return ERR_INVALID_ARG_COUNT;

            XortElement& argValue  = stack.get( dwStackBase - dwArgsCount );
            if( argValue.bType != t_u1 ) return ERR_INVALID_TYPE;

            retVal._u4 = hasChr( argValue._u1 );
            retVal.bType = t_u4;

            iRet = pRuntime->do_ret( retPrim ); // clean the XVM stack
            if( iRet != XORT_OK )
                return iRet;

            stack.push( retVal );
            break;
        }

    case 15:    // 15 void  dword2Str( dword d );
        {
            if( dwArgsCount != 1 ) // no arguments ??
                return ERR_INVALID_ARG_COUNT;

            XortElement& argValue  = stack.get( dwStackBase - dwArgsCount );
            if( argValue.bType != t_u4 ) return ERR_INVALID_TYPE;

            dword2Str( argValue._u4 );

            iRet = pRuntime->do_ret( retPrim ); // clean the XVM stack
            if( iRet != XORT_OK )
                return iRet;

            break;
        }

    case 16:    // 16 int ReverseFind( byte ch );
        {
            if( dwArgsCount != 1 ) // no arguments ??
                return ERR_INVALID_ARG_COUNT;

            XortElement& argValue  = stack.get( dwStackBase - dwArgsCount );
            if( argValue.bType != t_u1 ) return ERR_INVALID_TYPE;

            retVal._s4 = ReverseFind( argValue._u1 );
            retVal.bType = t_s4;

            iRet = pRuntime->do_ret( retPrim ); // clean the XVM stack
            if( iRet != XORT_OK )
                return iRet;

            stack.push( retVal );
            break;
        }
        
    case 17:    // 17 void Truncate( dword dwStartPos );
        {
            if( dwArgsCount != 1 ) // no arguments ??
                return ERR_INVALID_ARG_COUNT;

            XortElement& argValue  = stack.get( dwStackBase - dwArgsCount );
            if( argValue.bType != t_u4 ) return ERR_INVALID_TYPE;

            Truncate( argValue._u4 );
            
            iRet = pRuntime->do_ret( retPrim ); // clean the XVM stack
            if( iRet != XORT_OK )
                return iRet;

            break;
        }

    case 18:    // 18 int IsEmpty();
        {
            if( dwArgsCount != 0 ) // arguments ??
                return ERR_INVALID_ARG_COUNT;

            retVal._s4   = IsEmpty(); // invoke function
            retVal.bType = t_s4;

            iRet = pRuntime->do_ret( retPrim ); // clean the XVM stack
            if( iRet != XORT_OK )
                return iRet;

            stack.push( retVal ); // push the result of procedure
            break;
        }

    case 19:  // 19 string Substring( int iStartPos );
        {
            if( dwArgsCount != 1 ) // no arguments ??
                return ERR_INVALID_ARG_COUNT;

            XortElement& argValue  = stack.get( dwStackBase - dwArgsCount );
            if( argValue.bType != t_s4 ) return ERR_INVALID_TYPE;

            SYMBOLLIST symList = pRuntime->get_GlobalSymbolList();
            SymbolRef symString = FindType( symList, t_string );

            xstring *pString;
            iRet = pRuntime->createXObject( symString, NULL, (xobject**)&pString );
            if( iRet != XORT_OK )
                return iRet;

            if( argValue._u4 <= Length()  )
            {
                pString->Assign( &m_pchData[ argValue._u4 ] );
            }
                        
            iRet = pRuntime->do_ret( retPrim ); // clean the XVM stack
            if( iRet != XORT_OK )
                return iRet;

            retVal.SetValue( pString );
            retVal.bType = t_p;
            stack.push( retVal );
            break;
        }

    default:
        return ERR_METHOD_NOT_FOUND;
    }

    return XORT_OK; 
}

